using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Content;
using Momentum.Static;
using Momentum.Types;

namespace Momentum
{
    class mNetwork
    {
        #region instance variables

        ContentManager content;
        NetworkSession networkSession;
        PacketWriter packetWriter = new PacketWriter();
        PacketReader packetReader = new PacketReader();
        Momentum game;
        string errorMessage;
        mWorld world;
        int maxGamers;
        int team = 0;

        #endregion

        #region constructors

        public mNetwork(Momentum juego, ref mWorld mundo, int n_jugadores, ContentManager contendo) 
        {
            game = juego;
            world = mundo;
            maxGamers = n_jugadores;
            content = contendo;
        }

        #endregion

        #region status methods

        public bool IsHost()
        {
            return networkSession.IsHost;
        }

        public bool HasStarted()
        {
            return networkSession != null;
        }

        public string ErrorMessage()
        {
            return errorMessage;
        }

        public GamerCollection<NetworkGamer> AllGamers()
        {
            return networkSession.AllGamers;
        }

        public GamerCollection<LocalNetworkGamer> LocalGamers()
        {
            return networkSession.LocalGamers;
        }

        public void KillSession()
        {
            networkSession = null;
        }

        public bool IsEveryoneReady()
        {
            return networkSession.IsEveryoneReady;
        }

        public void StartGame()
        {
            networkSession.StartGame();
        }

        public NetworkSessionState SessionState()
        {
            return networkSession.SessionState;
        }

        #endregion

        #region startup methods

        /// <summary>
        /// Starts hosting a new network session.
        /// </summary>
        public void CreateSession()
        {
            try
            {
                networkSession = NetworkSession.Create(NetworkSessionType.SystemLink, 1, maxGamers);
                HookSessionEvents();
            }
            catch (Exception e)
            {
                errorMessage = e.Message;
            }
        }

        /// <summary>
        /// Joins an existing network session.
        /// </summary>
        public void JoinSession()
        {
            try
            {
                AvailableNetworkSessionCollection availableSessions = NetworkSession.Find(NetworkSessionType.SystemLink, 1, null);
                if (availableSessions.Count == 0)
                {
                    errorMessage = "No se encontraron sesiones.";
                    return;
                }
                /// TODO: Choose session to join.
                networkSession = NetworkSession.Join(availableSessions[0]);
                HookSessionEvents();
            }
            catch (Exception e)
            {
                errorMessage = e.Message;
            }
        }

        /// <summary>
        /// After creating or joining a network session, we must subscribe to
        /// some events so we will be notified when the session changes state.
        /// </summary>
        void HookSessionEvents()
        {
            networkSession.GamerJoined += GamerJoinedEventHandler;
            networkSession.SessionEnded += SessionEndedEventHandler;
        }

        /// <summary>
        /// This event handler will be called whenever a new gamer joins the session.
        /// </summary>
        public void GamerJoinedEventHandler(object sender, GamerJoinedEventArgs e)
        {
            int gamerIndex = networkSession.AllGamers.IndexOf(e.Gamer);

            mTank tank = content.Load<mTank>(@"Tanks/" + mStaticObject.TankNames[team]);
            tank.Position = new Vector2(mStaticWorld.Random.Next((int)mStaticWorld.Size.X), mStaticWorld.Random.Next((int)mStaticWorld.Size.Y));
            mWeapon weapon = content.Load<mWeapon>(@"Weapons/" + mStaticObject.BulletNames[0]);
            weapon.Load(content);
            e.Gamer.Tag = new mPlayer(tank, weapon, team);
            team++;
            team %= 2;
        }

        /// <summary>
        /// Event handler notifies us when the network session has ended.
        /// </summary>
        void SessionEndedEventHandler(object sender, NetworkSessionEndedEventArgs e)
        {
            errorMessage = e.EndReason.ToString();
            networkSession.Dispose();
            networkSession = null;
        }

        #endregion

        #region update methods

        /// <summary>
        /// Updates the state of the network session, moving the tanks
        /// around and synchronizing their state over the network.
        /// </summary>
        public void UpdateNetworkSession()
        {

            // Make sure the session has not ended.
            if (networkSession == null)
                return;

            // Pump the underlying session object.
            networkSession.Update();

            // Read (server) or write (client) any incoming network packets.
            foreach (LocalNetworkGamer gamer in networkSession.LocalGamers)
            {
                if (gamer.IsHost)
                {
                    ServerReadInputFromClients(gamer);
                }
                else
                {
                    ClientReadGameStateFromServer(gamer);
                }
            }
        }

        #region host

        /// <summary>
        /// Envia la informacion de todos los tanques
        /// </summary>
        public void sendPLayerData()
        {
            // set the packet type
            packetWriter.Write((int)mStaticNetwork.PacketTypes.GameState);

            //our packet will indicate how many tanks it has data for.
            packetWriter.Write(networkSession.AllGamers.Count);

            // Loop over all the players in the session, not just the local ones!
            foreach (NetworkGamer gamer in networkSession.AllGamers)
            {
                mPlayer player = gamer.Tag as mPlayer;

                // Write the tank state into the output network packet.
                player.tank.SerializeWH(packetWriter);
                player.weapon.SerializeWH(packetWriter);
            }

            // Send the combined data for all tanks to everyone in the session.
            LocalNetworkGamer server = (LocalNetworkGamer)networkSession.Host;
            server.SendData(packetWriter, SendDataOptions.InOrder);
        }

        /// <summary>
        /// Envia la informacion de los objetos del mundo.
        /// </summary>
        public void sendWorldData()
        {
            packetWriter.Write((int)mStaticNetwork.PacketTypes.WorldData);
            packetWriter.Write(world.Objects.Count);
            foreach (mObject obj in world.Objects)
            {
                obj.SerializeWH(packetWriter);
            }
            packetWriter.Write(world.Explosions.Count);
            foreach (Vector2 pos in world.Explosions)
            {
                packetWriter.Write(pos);
            }
            LocalNetworkGamer server = (LocalNetworkGamer)networkSession.Host;
            server.SendData(packetWriter, SendDataOptions.InOrder);
        }

        /// <summary>
        /// This method only runs on the server. It reads tank inputs that
        /// have been sent over the network by a client machine, storing
        /// them for later use by the UpdateServer method.
        /// </summary>
        void ServerReadInputFromClients(LocalNetworkGamer gamer)
        {
            // Keep reading as long as incoming packets are available.
            while (gamer.IsDataAvailable)
            {
                NetworkGamer sender;

                // Read a single packet from the network.
                gamer.ReceiveData(packetReader, out sender);

                if (!sender.IsLocal)
                {
                    //soy el host
                    //obtengo el tipo
                    mStaticNetwork.PacketTypes packetType = (mStaticNetwork.PacketTypes)packetReader.ReadInt32();

                    switch (packetType)
                    {
                        case mStaticNetwork.PacketTypes.PlayerData:
                            // Look up the tank associated with whoever sent this packet.
                            mPlayer remotePlayer = sender.Tag as mPlayer;

                            // Read the latest inputs controlling this tank.
                            remotePlayer.Deserialize(packetReader);
                            break;
                    }
                }
            }
        }

        #endregion

        #region client

        /// <summary>
        /// Helper for updating a locally controlled gamer.
        /// </summary>
        public void UpdateLocalGamer(LocalNetworkGamer gamer)
        {
            mPlayer localPlayer = gamer.Tag as mPlayer;

            // Only send if we are not the server. There is no point sending packets
            // to ourselves, because we already know what they will contain!
            if (!networkSession.IsHost)
            {
                // Write our latest input state into a network packet.
                localPlayer.Serialize(packetWriter);

                // Send our input data to the server.
                gamer.SendData(packetWriter, SendDataOptions.InOrder, networkSession.Host);
            }
        }

        /// <summary>
        /// This method only runs on client machines. It reads
        /// tank position data that has been computed by the server.
        /// </summary>
        void ClientReadGameStateFromServer(LocalNetworkGamer gamer)
        {
            // Keep reading as long as incoming packets are available.
            while (gamer.IsDataAvailable)
            {
                NetworkGamer sender;

                // Read a single packet from the network.
                gamer.ReceiveData(packetReader, out sender);

                //obtengo el tipo
                mStaticNetwork.PacketTypes packetType = (mStaticNetwork.PacketTypes)packetReader.ReadInt32();

                switch (packetType)
                {
                    case mStaticNetwork.PacketTypes.GameState:
                        // If a player has recently joined or left, it is possible the server
                        // might have sent information about a different number of players
                        // than the client currently knows about. If so, we will be unable
                        // to match up which data refers to which player. The solution is
                        // just to ignore the packet for now: this situation will resolve
                        // itself as soon as the client gets the join/leave notification.
                        if (networkSession.AllGamers.Count != packetReader.ReadInt32())
                            continue;

                        // This packet contains data about all the players in the session.
                        foreach (NetworkGamer remoteGamer in networkSession.AllGamers)
                        {

                            mPlayer player = remoteGamer.Tag as mPlayer;

                            // Read the state of this tank from the network packet.
                            player.tank.Deserialize(packetReader);
                            player.weapon.Deserialize(packetReader);
                        }
                        break;
                    case mStaticNetwork.PacketTypes.WorldData:
                        world.Objects.Clear();
                        int count = packetReader.ReadInt32();
                        for (int i = 0; i < count; i++)
                        {
                            mBox box = new mBox();
                            box.Deserialize(packetReader);
                            box.Load(content);
                            world.Objects.Add(box);
                        }
                        world.Explosions.Clear();
                        count = packetReader.ReadInt32();
                        for (int i = 0; i < count; i++)
                        {
                            Vector2 pos = packetReader.ReadVector2();
                            world.Explosions.Add(pos);
                        }
                        break;
                }
            }
        }

        #endregion

        #endregion
    }
}
